iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 17
1
Software Development

你會十五種程式語言?不,我會十五種HelloWorld.為了避免這種狀況,因此寫了這篇:淺入淺出十五種程式語言系列 第 17

Raku 簡單的事情應該保持簡單,複雜的事情應該讓它變得簡單,不可能的事情應該讓他變得可能

  • 分享至 

  • xImage
  •  

寫在前面

Raku原本的名字是Perl6,於2019年10月正式更名為Perl6,
主要原因是因為作者認為語法上跟上一代的Perl5差異太大,因此獨立出來成為一門新的語言
但是perl7還是會推出,所以實際上應該會變成兩個獨立的版本,概念上類似python2跟python3

Perl跟python一樣內建在某些版本的linux(Mac不清楚,但聽說有)
如果跟我一樣使用linux的可以嘗試看看下面的程式碼吧

perl -e 'print "Hello world!\n"'

應該會跟當初的我一樣嚇到
阿我的電腦裡面怎麼有這種東西

不過我們今天要學的是新的Raku,目前還沒有流行到會預設裝在系統裡面,因此等一下我們依舊會使用docker

哲學

雖然前面提到很多次python跟Perl的類似(甚至他們都是名次為P開頭的語言)
但實際上兩者的開發理念是完全相反的
python的哲學是

There should be one– and preferably only one –obvious way to do it

只有一種方法,而且最好只有一種來做事

而Perl則是

There is more than one way to do it

不只一種方法來做一件事

因此兩者實際上在理念是有差異的
寫起來的感覺上,python更像在做程式開發,而Raku則是像在寫文章

先去DockerHub找Raku,然後我們開始吧

等一下,我找不到Raku
喔...我發現了rakudo,是這個嗎?

rakudo是Raku這門語言的編譯器實現
可以將按照Raku語法的檔案轉譯成機器語言,使電腦可以執行

所以去抓rakudo-star的image吧

Raku

先建立一個附檔名為p6的檔案(或是.raku,.pl6都可以)

接著貼上下面的程式

sub t2h($input){
    loop (my $i = 0; $i < 16; $i++) {
        loop (my $j = 0 ;$j < 16;$j++){
            loop (my $k = 0;$k < 16;$k++){
                return sprintf "%s%s%s", returnAE($i) , returnAE($j), returnAE($k) if (16**2*$i+16**1*$j+16**0*$k==$input) 
            }
        }
    }
}

sub returnAE($input) {
    return $input if $input < 10;

    given $input {
        when 10 { return "A"; }
        when 11 { return "B"; }
        when 12 { return "C"; }
        when 13 { return "D"; }
        when 14 { return "E"; }
        when 15 { return "F"; }
    }
}

######################################################

sub h2t($input){
    my @arr = $input.split("",:skip-empty);
    my $output = 0;
    my $i = 0;
    while $i < @arr.elems {
        $output += AEreturn(@arr[$i])*16**(@arr.elems-$i-1);
        $i++;
    }
    $output ;
}

sub AEreturn($input){
    given $input {
        when "A" { return 10; }
        when "B" { return 11; }
        when "C" { return 12; }
        when "D" { return 13; }
        when "E" { return 14; }
        when "F" { return 15; }
    }
    $input;
}

##############################

my $choose;

say "Tell me what you want to do:";
say "(1)T to H (2)H to T";
$choose = get;

given $choose {
    when "1" {
        say "Please enter the number";
        my $number = get;
        t2h($number).say;
    }
    when "2" {
        say "Please enter the number";
        my $number = get;
        h2t($number).say;
    }
    default {
        die "Wrong select";
    }
}

使用

docker container run --rm -it -v $PWD:/home/raku rakudo-star:alpine raku /home/raku/convert.p6

就可以執行raku檔拉

注意Raku是很要求風格的語言,有時候在某些地方多了或少了空格程式就跑不起來

如果你有遇到某些狀況,請檢查一下程式碼

結構

跟大部分好寫的語言一樣,Raku是直譯式語言,所以程式進入點就在程式碼的第一行

與Ruby不同點在於Raku需要在行尾使用;

註解使用#
如果你需要多行註解可以這樣寫

=begin pd
This is a multi line comment.
Comment 1
Comment 2
=end pd

變數宣告

跟之前一樣先往下看到程式本體吧

my $choose;

這個是Raku的變數宣告
還記得以前使用過的var吧
my是類似的東西

另外雖然php的變數都也都使用$開頭
但是在Raku實際有好幾種,以下是常見的三種

$choose #表示一般變數
@choose #表示陣列
%choose #表示雜湊表(目前可以先不管他,這次不會用到)

所以雖然Raku屬於動態型別,不必確實宣告變數的型別,但是還是要告訴Raku變數的種類

輸入與輸出

上一行宣告出變數之後我們就可以使用他來接輸入了

say "Tell me what you want to do:";
say "(1)T to H (2)H to T";
$choose = get;

是不是跟Ruby一樣簡單呢?

邏輯

接下來我們看到Raku的switch

given $choose {
    when "1" {
        say "Please enter the number";
        my $number = get;
        t2h($number).say;
    }
    when "2" {
        say "Please enter the number";
        my $number = get;
        h2t($number).say;
    }
    default {
        die "Wrong select";
    }
}

Ruby使用Case...when
而Raku使用given...when
跟Ruby一樣不需要break,但是需要{}

注意到這裡

t2h($number).say;

這是另外一種印出回傳值的方式,直接在後面加.say

當然,你也可以像往常一樣這樣印

say t2h($number);

default裡頭的die則是Raku拋出異常的寫法
你可以嘗試故意輸入錯誤,看看Raku的錯誤是怎麼拋出的

方法

往上看到副程式吧

sub t2h($input){
    loop (my $i = 0; $i < 16; $i++) {
        loop (my $j = 0 ;$j < 16;$j++){
            loop (my $k = 0;$k < 16;$k++){
                return sprintf "%s%s%s", returnAE($i) , returnAE($j), returnAE($k) if (16**2*$i+16**1*$j+16**0*$k==$input) 
            }
        }
    }
}

Raku的方法關鍵字用sub
輸入的部份由於需要命名變數名稱,因此記得加上$之類的前綴
與其他動態語言相同,不需要指定輸入與輸出的型別

迴圈

loop (my $i = 0; $i < 16; $i++) {

Raku的迴圈使用loop,類似以往我們寫的for,裡面的東西也跟我們之前寫for迴圈時類似
記得有()跟{}

邏輯判斷if

Raku有一種很特別的if寫法

return sprintf "%s%s%s", returnAE($i) , returnAE($j), returnAE($k) if (16**2*$i+16**1*$j+16**0*$k==$input) 

他可以把return寫在前面,if在後面
這種寫法更像是在寫文章而不是在寫程式,尤其是if裡面只有一個return時是最方便的
當然,你也可以這樣寫

if 16**2*$i+16**1*$j+16**0*$k==$input {
    return sprintf "%s%s%s", returnAE($i) , returnAE($j), returnAE($k)
}

if的()可加可不加

冪次計算

注意到if裡面計算冪次的方式

if (16**2*$i+16**1*$j+16**0*$k==$input)

跟Ruby相同使用**作為次方的運算

型別系統

Raku採用弱型別系統,因此我們不需要將輸入的值$input給轉型就可以跟數值做比較
這點跟Ruby就不同了,Ruby雖然是動態型別,但採用強型別系統,字串沒做轉型之前沒辦法跟數值做比較

因此你可以看到這樣寫

if (16**2*$i+16**1*$j+16**0*$k==$input) 

字串處理

讓我們看到另外一支將十六進位轉成十進位的方法,看看Raku是怎麼處理字串的

sub h2t($input){
    my @arr = $input.split("",:skip-empty);
    my $output = 0;
    my $i = 0;
    while $i < @arr.elems {
        $output += AEreturn(@arr[$i])*16**(@arr.elems-$i-1);
        $i++;
    }
    $output ;
}

在其他語言我們可以把字串當作字元陣列來使用,
但是在Raku由於是把一般變數跟陣列當作不同型別來看,因此我們這邊需要把字串分割成陣列
這裡使用.split這個方法,分割的基準採用""(也就是什麼都沒有)這樣就會把文字一個一個切出來
skip-empty則是捨棄分割時前後可能會產生的空字元,詳細的用法可以看這裡
這樣我們就將字串分割完了

接著我們把他丟入@arr這個變數裡(記得@是陣列的前綴,而my則是宣告的關鍵字吧)

你可以嘗試將程式碼更改如下

sub h2t($input){
    my @arr = $input.split(""); #更改這一行
    my $i = 0;
    my $output = 0;
    while $i < @arr.elems {
        @arr[$i].say; # 印出每一個字元
        $i++;
    }
    $output ;
}

看看如果沒有skip-empty的話印出來會長什麼樣子

邏輯while

我們來看看裡面的while吧

while $i < @arr.elems {
    $output += AEreturn(@arr[$i])*16**(@arr.elems-$i-1);
    $i++;
}

while跟ruby一樣條件不需要加(),但是需要{}

裡頭的

@arr.elems

則是取得陣列的長度,相當於以前的number.length

方法的回傳值

中間的算法基本跟以前一樣,我們往下看到最後一行

$output ;

這裡的return可加可不加
Raku預設會將方法的最後一行回傳

因此AEreturn的最後一行

$input;
也是相同意思

以上就是Raku的基本語法了

補充一下幾個沒介紹到的地方

Raku的方法輸入值視為常數

sub add($input is copy) { #傳入的參數通常不能修改,因此要用is copy
    $input += 1;
    $input;
}
 
my $num = 10;
say "right" if add($num)==11

上面的方法如果沒有is copy會報錯

raku

Raku也有像是Ruby的irb可以使用指令模式

如果你點進去image的tag的話就可以看到他是怎麼寫dockerfile的了
https://ithelp.ithome.com.tw/upload/images/20200917/201278367IXyAB5FcE.png
有看到最後一行寫著

CMD ["raku"]

嗎?
raku其實就是Ruby的irb,使用指令模式的指令

試試看下面的docker命令吧

docker container run --rm -it rakudo-star:alpine

記得docker的tag要換成你們自己的

就可以開始玩Raku了

語法複習

  • 基本結構
    • 跟ruby一樣直譯式語言,從程式碼第一行開始執行
    • 行尾需要;
  • 印出/讀取
    • 使用say跟get
    • say可以當作是物件的方法加在最後面
  • 方法的結構
    • 使用sub宣告
    • 動態型別,不必提前命名回傳值的型別
    • 使用{}來宣告方法的範圍
    • 預設回傳方法的最後一行
  • 邏輯控制
    • given 就是以往的switch,使用{}做範圍宣告,不需要break
    • if 不需要()跟,可以將if放在要做的事後面
  • 迴圈控制
    • loop 跟一般的for一樣
    • while 跟golang一樣不需要在條件使用()
  • 型別
    • 跟js一樣屬於動態+弱型別
  • 冪次計算
    • 使用**而不使用一般的pow
  • 宣告
    • 使用my
    • 需要有特殊的前綴@$%等等

小結

其實在介紹Ruby之前應該先介紹Raku的
這樣才有歷史傳承的感覺
但是寫著覺得這樣介紹起來比較順手,便這樣寫下去了

Raku算是寫起來比較開心的語言之一
雖然golang跟ruby寫起來也很舒服,但是

return $input if $input < 10;
#以及
@arr[$i].say;

這種語法寫起來太開心了

但是缺點也不是沒有,就是資源太少了
可能是因為還在Raku跟Perl6的名稱轉型期,因此同一筆搜尋可能要用perl6跟Raku兩種關鍵字去找
不過這點在未來應該會改善
另一個缺點則是直譯器的偵錯不太明顯
有時候只是行尾少了;沒注意到就報錯,而且還只是大概位置
比方說

when "1" {
    say "Please enter the number" #把這裡的;拿掉
    my $number = get;
    t2h($number).say;
}

執行時會報錯

at /home/raku/convert.p6:62
------>         say "Please enter the number"⏏<EOL

但實際上錯誤的是上一行

或是

when "1"{ #把"跟{中間的空格拿掉

會報錯

Missing block
at /home/raku/convert.p6:66
------>     when ⏏"2"{

但是明明錯誤的不是第66行

直譯器的缺點未來應該都會改善,不然當專案越來越大時會越來越難找到問題點
(偏偏Raku還是直譯式語言,執行時才知道問題在哪)

因此我們來學一個編譯器超強的語言
而且還跟昨天今天一樣由R開頭
他就是鐵鏽
Rust

不過在那之前,我們來學型別吧


上一篇
Ruby 那不是Rails的一個框架嗎?
下一篇
型別 沒有沒有型別的值
系列文
你會十五種程式語言?不,我會十五種HelloWorld.為了避免這種狀況,因此寫了這篇:淺入淺出十五種程式語言30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
skycover
iT邦新手 4 級 ‧ 2020-09-17 21:44:49

如果有任何寫不清楚或是觀念沒有很明白的話請留言告知我
會盡快補上

如果有任何寫錯的地方也麻煩留言告知我
會盡快修正

感謝各位

我要留言

立即登入留言